let 和 const 命令
1.let
概念:
-
暂时性死区(TDZ)
- 在作用域内,let变量已经存在;
并绑定该作用域:不受外界影响,外界也不可获取
let作用域以代码块划分,var作用域则只有全局和函数 - 在let声明之前,不可获取;(也就是TDZ)
- 在let声明之后,才可获取。
- 在作用域内,let变量已经存在;
- let变量不可重复声明
- let不允许在相同作用域内,重复声明同一个变量。
tip:
- TDZ也解释了为什么let不存在变量提升。
- ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
2.块级作用域
-
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景,如:
- 内层变量可能会覆盖外层变量。
- 用来计数的循环变量泄露为全局变量。
-
ES6 的块级作用域(let实际上为 JavaScript 新增了块级作用域),作用:
- 外层作用域无法读取内层作用域的变量。
- 内层作用域可以定义外层作用域的同名变量。
- 使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
- 根据ES6 在附录 B里的规定,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。
3.const 命令
- const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
-
冻结对象:
const foo = Object.freeze({});
-
彻底冻结对象的函数:
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach((key, i) => if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。
4.顶层对象的属性
从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
变量的解构赋值
1.数组的解构赋值
基本用法:
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
默认值
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
上面最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。
2.对象的解构赋值
- 命名与键名相同则简写:
let { bar, foz } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // undefined
- 命名与键名不同:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
- 嵌套:
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
obj // {prop:123}
arr // [true]
- 默认值:
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x: y = 3} = {};
y // 3
var {x: y = 3} = {x: 5};
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
- 如果要将一个已经声明的变量用于解构赋值,必须非常小心:
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({x} = {x: 1});
- 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
3.字符串的解构赋值
- 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
- 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
4.数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
5.函数参数的解构赋值
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。
注意,下面的写法会得到不一样的结果。
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
6.圆括号问题
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不属于模式的一部分。
第一行语句中,模式是取数组的第一个成员,跟圆括号无关;
第二行语句中,模式是p,而不是d;
第三行语句与第一行语句的性质一致。
7.用途
(1)交换变量的值
(2)从函数返回多个值
(3)函数参数的定义
(4)提取 JSON 数据
(5)函数参数的默认值
(6)遍历 Map 结构
(7)输入模块的指定方法
数组的拓展
1.扩展运算符(...)
用法简单,写ES6的时候留意下是否可以用之与简化代码即可,
注意点:
解构赋值的时候必须放在末尾,否则报错(Rest element must be last element)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。